#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <string>
#include <fstream>
#include <streambuf>
#include <sstream>
#include <cstdlib>
#include <new>
#include <typeinfo>
#include <list>
#include <thread> // std::thread
#include <mutex>  // std::mutex

using namespace std;

// 参考: https://blog.csdn.net/misayaaaaa/article/details/53786215
// 参考: https://github.com/applenob/Cpp_Primer_Practice
// chapter 16 模板和范型编程

// 1. 给出实例化的定义
// 当编译器实例化一个模版时，它使用实际的模版参数代替对应的模版参数来创建出模版的一个新“实例”
// 知识点1：面对对象编程OOP和泛型编程都能处理在编写程序时不知道类型的情况，而OOP能处理类型在程序运行之前都未知的情况，在泛型编程中，在编译时就能获知类型了
// 知识点2：容器、迭代器、算法都是泛型编程的例子
// 知识点3：模版是C++泛型编程的基础，一个模版就是一个类或函数的蓝图或者说是公式：例如在使用vector这样的泛型类型，或者是find函数这样的泛型类型，我们可以将蓝图转换为特定的类或者是函数，这种转换发生在编译时，16章主要学习如何定义一个模版
// 知识点4：一个函数模版就是一个公式，可用来生成指定类型的函数版本
// 知识点5：模版定义从template开始，后跟一个模版参数列表，用两个小尖括号包起来，参数列表不能为空
// 知识点6：当我们调用template时，编译器会使用实参的类型来确定绑定到模版参数T上的类型，之后编译器利用推断出的模版参数来实例化一个特定版本的函数，这个过程被称之为实例化

// 2. 编写并测试你自己版本的 compare 函数
// 知识点：编译器遇到一个模版的定义时，并不会产生代码，只有当我们实例化出模版的一个特定版本的时候，编译器才会产生代码
// T表示一个类型，而T的实际类型在编译时根据实际情况决定
template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 > v2)
    {
        return 1;
    }
    else
    {
        return -1;
    }
    return 0;
}

// 3.对两个 Sales_data 对象调用你的 compare 函数，观察编译器在实例化过程中如何处理错误。

解：

error: no match for 'operator<'
// 知识点1：可以将模版的类型参数看作是类型说明符，类型参数可以用来指定函数的返回类型、函数的参数类型以及在函数体内用于变量的声明和类型转换，
//         在类型参数之前必须加上typename或者class这两个关键字，这两个关键字含义相同，可以互换使用，可以定义多个类型参数
// 知识点2：使用typename更好一点，因为我们还可以使用内置类型(非类)作为模版的类型参数
// 知识点3：模版直到实例化的时候才会产生代码，所以我们传入的实参类型在函数的体内需要满足各项的操作，比如书中例子使用了<这个运算符，而有些类类型并没有定义此运算符，这样就会报错
// 知识点4：保证传递给模版的实参支持模版的所有操作，以及这些操作在模版中正确工作，非常重要

class Sales_data
{
};

// 4. 编写行为类似标准库 find 算法的模版。函数需要两个模版类型参数，一个表示函数的迭代器参数，另一个表示值的类型。使用你的函数在一个 vector<int> 和一个list<string>中查找给定值
template <typename U, typename T>
bool my_find(const U &it1, const U &it2, const T &val)
{
    auto it3 = it1; //传入参数为const，不能做修改，需要一个临时变量！
    for (it3; it3 != it2; ++it3)
    {
        if (*it3 == val)
        {
            return true;
        }
    }
    return false;
}






// 5. 为6.2.4节中的print函数编写模版版本，它接受一个数组的引用，能处理任意大小、任意元素类型的数组
// 知识点1：泛型编程代码的两个重要的原则：模版中的函数参数是const的引用——若需要使用中修改，可以拷贝一份副本。模版程序应该尽量减少对实参类型的要求——所用运算符需要支持实参的类型
template <typename T>
void my_print(const T &arr)
{
    for (auto item : arr)
    {
        cout << item << endl;
    }
}






// 6.  你认为接受一个数组实参的标准库函数 begin 和 end 是如何工作的？定义你自己版本的 begin 和 end
// 知识点1：非类型模版参数，可以使用一个非类型参数（一个值或者指针和引用）来指定非类型参数，个人觉得相当于宏定义#define，特殊的就是指针和引用的实参必须与静态的生存期
// 知识点2：编译器会使用字面常量的大小代替非类型参数，若是字符串还会加一
// begin()是一个模版函数，接受一个容器的引用，返回指向容器第一个元素的迭代器
// end()是一个模版函数，接受一个容器的引用，返回指向容器最后一个元素下一个地方的迭代器

template <typename T, unsigned N>
T begin(const T (&arr)[N])
{
    return arr;
}

template <typename T, unsigned N>
T end(const T (&arr)[N])
{
    return (arr + N);
}





// 7. 编写一个 constexpr 模版，返回给定数组的大小
// 知识点：函数模版可以声明为inline或是constexpr，置于模版参数列表之后，函数返回类型之前
template <typename T, size_t N>
constexpr int arr_size(const T (&a)[N])
{
    return N;
}


// 8. 在第97页的“关键概念”中，我们注意到，C++程序员喜欢使用 != 而不喜欢 < 。解释这个习惯的原因
// 原因是基本上很多的类型都定义了！= 这个运算符，而不一定定义了 <  这个运算符，见第三题的知识点3、4

// 9. 什么是函数模版，什么是类模版？
// 函数模板
// 一个通用的函数模板（function template）就是一个公式，可用来生成针对特定类型或特定值的函数版本。模板定义以关键字template开始，后面跟一个模板参数列表，列表中的多个模板参数（template parameter）以逗号分隔。模板参数表示在类或函数定义中用到的类型或值
// 类模板
// 相应的，类模板（class template）是用来生成类的蓝图。与函数模板的不同之处是，编译器不能为类模板推断模板参数类型，所以我们必须显式的提供模板实参。与函数模板一样，类模板参数可以是类型参数，也可以是非类型参数

// 10. 当一个类模版被实例化时，会发生什么？
// 使用显示模版实参初始化一个类
// 一个类模版的每个实例都形成一个独立的类

// 11. 下面 List 的定义是错误的。应如何修改它？

template <typename elemType> class ListItem;
template <typename elemType> class List {
public:
	List<elemType>();
	List<elemType>(const List<elemType> &);
	List<elemType>& operator=(const List<elemType> &);
	~List();
	void insert(ListItem *ptr, elemType value);
private:
	ListItem *front, *end;
};
解：

模版需要模版参数，应该修改为如下：

template <typename elemType> class ListItem;  
template <typename elemType> class List{  
public:  
  	List<elemType>();  
  	List<elemType>(const List<elemType> &);  
  	List<elemType>& operator=(const List<elemType> &);  
  	~List();  
  	void insert(ListItem<elemType> *ptr, elemType value);  
private:  
  	ListItem<elemType> *front, *end;  
};



// 知识点11：在类模版自己的作用域中（即类内），我们可以直接使用模版名而不提供实参（不需要<>这个东西了）而在类外则需要指定模版参数（返回值是模版的类型）
// 在List类内，使用模版名不需要再加参数列表，而ListItem使用时必须加上<>模版参数列表
template <typename elemType>
class ListItem;

template <typename elemType>
class List
{
public:
    List<elemType>();
    List<elemType>(const List<elemType> &);

private:
    ListItem<elemType> *front, *endl;
    // ListItem *front,*endl;  // 错误 // 无论何时使用模版都必须提供模版实参
};

template <typename T>
class Blob
{
public:
    Blob();
    void check(size_t, const string &) const;

private:
    shared_ptr<vector<T>> data;
};

shared_ptr<vector<int>> data_tmp;
template <typename T>
Blob<T>::Blob() : data(data_tmp){}; //类外定义构造函数

template <typename T>
void Blob<T>::check(size_t i, const string &msg) const
{ //类外定义成员函数
}

typedef Blob<string> Blob_str; //之后使用Blob_str就是string类型的了

template <typename T>
using twin = Blob<T>; //twin代指Blob




// 12.







// 17.
// typename 和 class的区别
// 没有什么不同。当我们希望通知编译器一个名字表示类型时，必须使用关键字 typename，而不能使用 class
知识点1：T没有任何的实际意义，只是类型的一个替代

知识点2：模版参数的作用于范围在其声明之后，模版的定义或声明结束之前，且其会隐藏外层作用域中相同的名字(在外层使用了typedef T之后，在类中T的含义还是模版参数的含义)，且模版参数名不可才参数列表中重复

知识点3：声明与定义中的模版参数的名字可以不相同

知识点4：我们只能使用关键字typename关键字来表明一个名字表示的是类型

知识点5：C++11新标准允许默认模版实参（可以为函数提供，早起只能为类模版提供默认模版实参）

知识点6：在我们为类模版的所有参数都提供了默认参数后，加一对空尖括号即可



见知识点4，且typename还可以绑定内置类型，而class只能绑定类类型






// 18.
解释下面每个函数模版声明并指出它们是否非法。更正你发现的每个错误。

(a) template <typename T, U, typename V> void f1(T, U, V);
(b) template <typename T> T f2(int &T);
(c) inline template <typename T> T foo(T, unsigned int *);
(d) template <typename T> f4(T, T);
(e) typedef char Ctype;
	template <typename Ctype> Ctype f5(Ctype a);
解：

(a) 非法。应该为 template <typename T, typename U, typename V> void f1(T, U, V);。
(b) 非法。应该为 template <typename T> T f2(int &t);
(c) 非法。应该为 template <typename T> inline T foo(T, unsigned int*);
(d) 非法。应该为 template <typename T> T f4(T, T);
(e) 非法。Ctype 被隐藏了

// template<typename T,U,typename V>
// void f1(T,U,V);
// 错误，不能连续声明模板参数 改为:
template <typename T, typename U, typename V>
void f1(T, U, V);

// template<typename T>
// T f2(int& T)
// 错误，模板参数名不能作为变量名 改为:
template <typename T>
T f2(T &);

// inline template<typename T>
// T foo(T, unsigned int*);
template <typename T>
inline T foo(T, unsigned int *); // 在模板定义的时候使用inline  // 内联声明位置错误

// template<typename T>
// f4(T,T)
// 缺少返回类型
template <typename T>
T f4(T, T);

typedef char Ctype;
template <typename Ctype>
Ctype f5(Ctype a);




// 19. 编写函数，接受一个容器的引用，打印容器中的元素。使用容器的 size_type 和 size成员来控制打印元素的循环
template <typename C>
void print(const C &c)
{
    // size_type 是容器的类型成员而不是类的静态成员,所以用typename 特别指出
    for (typename C::size_type i = 0; i < c.size(); i++)
    {
        cout << c.at(i) << " " << endl;
    }
}




// 20.


// 21.
// 知识点1：一个类无论是类模版还是普通类，都可以将其成员函数定义为模版，称之为成员模版，但是成员模版不能是虚函数
class Debugdelete
{
public:
    Debugdelete(ostream &s = cerr) : os(s) {} //构造函数
    template <typename T>
    void operator()(T *p) const
    {
        os << "delete ...  " << endl;
        delete p;
    }

private:
    ostream &os; //私有成员为一个输出流
};

// 22.
// 可以对类型的删除器进行重载我们自己定义的版本，在尖括号中给出删除器的类型，并提供一个这个类型的对象给它的构造函数即可

unique_ptr<int, Debugdelete> m(new int, Debugdelete());
shared_ptr<int> n(new int, Debugdelete());

// 23.



// 24. 为你的 Blob 模版添加一个构造函数，它接受两个迭代器
// 在类模版内定义模版函数，主要的区别就是他们有着自己独立的模版参数，所以我们在类模版外定义一个模版成员函数时，必须同时为类模版和成员模版提供模版参数
template <typename T>
class Blob_1
{
public:
    template <typename IT>
    Blob_1(IT &, IT &); //声明
private:
    shared_ptr<vector<T>> data;
};

//类外定义,首先需要声明类模版参数列表，还要加上成员自己的模版参数列表，还有类作用域Blob_1<T>
template <typename T>
template <typename IT>
Blob_1<T>::Blob_1(IT &a, IT &b) : data(make_shared<vector<T>>(a,b)) {}


// 25. 解释下面这些声明的含义。

extern template class vector<string>;
template class vector<Sales_data>;
解：

前者是模版声明，后者是实例化定义
extern template class Blob<string>; // extern表示不在本文本中生成实例化代码,该实例化的定义会在其他程序中

template class vector<Sales_data>; // 用sales_data实例化 vector


// 26. 假设 NoDefault 是一个没有默认构造函数的类，我们可以显式实例化 vector<NoDefualt>吗？如果不可以，解释为什么。

解：

不可以。如

std::vector<NoDefault> vec(10);
会使用 NoDefault 的默认构造函数，而 NoDefault 没有默认构造函数，因此是不可以的



// 27. 对下面每条带标签的语句，解释发生了什么样的实例化（如果有的话）。如果一个模版被实例化，解释为什么;如果未实例化，解释为什么没有。

template <typename T> class Stack {	};
void f1(Stack<char>); 		//(a)
class Exercise {
	Stack<double> &rds;		//(b)
	Stack<int> si;			//(c)
};
int main() {
	Stack<char> *sc;		//(d)
	f1(*sc);				//(e)
	int iObj = sizeof(Stack<string>);	//(f)
}
解：

(a)、(b)、(c)、(f) 都发生了实例化，(d)、(e) 没有实例化

template<typename T>
class Stack{};

void f1(Stack<char>); // stack对char的实例化

class Exerise{
    Stack<double>& rsd; // stack对double的实例化
    Stack<int> si; // stack对int的实例化
};



// 28.

// 29.


// 30.


// 31.




// 32. 在模版实参推断过程中发生了什么？

解：

在模版实参推断过程中，编译器使用函数调用中的实参类型来寻找模版实参，用这些模版实参生成的函数版本与给定的函数调用最为匹配





// 33. 指出在模版实参推断过程中允许对函数实参进行的两种类型转换。

解：

const 转换：可以将一个非 const 对象的引用（或指针）传递给一个 const 的引用（或指针）形参。
数组或函数指针转换：如果函数形参不是引用类型，则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的，一个函数实参可以转换为一个该函数类型的指针。





// 34. 对下面的代码解释每个调用是否合法。如果合法，T 的类型是什么？如果不合法，为什么？

template <class T> int compare(const T&, const T&);
(a) compare("hi", "world");
(b) compare("bye", "dad");
解：

(a) 不合法。compare(const char [3], const char [6]), 两个实参类型不一致。
(b) 合法。compare(const char [4], const char [4])





// 35. 下面调用中哪些是错误的（如果有的话）？如果调用合法，T 的类型是什么？如果调用不合法，问题何在？

template <typename T> T calc(T, int);
tempalte <typename T> T fcn(T, T);
double d; float f; char c;
(a) calc(c, 'c'); 
(b) calc(d, f);
(c) fcn(c, 'c');
(d) fcn(d, f);
解：

(a) 合法，类型为char
(b) 合法，类型为double
(c) 合法，类型为char
(d) 不合法，这里无法确定T的类型是float还是double

(a)：合法，第一个为char，即为T，第二个为char [1]，可以进行类型转换

(b)：合法，T为double，clac第二个参数为普通类型的int，可以进行算数类型转换

(c)：不合法，一个char，一个char[1]

(d)：不合法，一个double一个float，无法进行类型转换


// 36. 进行下面的调用会发生什么：

template <typename T> f1(T, T);
template <typename T1, typename T2) f2(T1, T2);
int i = 0, j = 42, *p1 = &i, *p2 = &j;
const int *cp1 = &i, *cp2 = &j;
(a) f1(p1, p2);
(b) f2(p1, p2);
(c) f1(cp1, cp2);
(d) f2(cp1, cp2);
(e) f1(p1, cp1);
(f) f2(p1, cp1);
解：

(a) f1(int*, int*);
(b) f2(int*, int*);
(c) f1(const int*, const int*);
(d) f2(const int*, const int*);
(e) f1(int*, const int*); 这个使用就不合法
(f) f2(int*, const int*);

(a)：合法，T为 int*

(b)：合法，T1和T2都为int*

(c)：合法，顶层const会被忽略掉，T为int*

(d)：合法，T1和T2都为int*

(e)：不合法，首先需要判断实参的类型是否相同，再判断类型是否可转换，两参数一个为const一个非const

(f)：hefa，T1和T2都为int*



// 37.





// 38.


// 42. 对下面每个调用，确定 T 和 val 的类型：

template <typename T> void g(T&& val);
int i = 0; const int ci = i;
(a) g(i);
(b) g(ci);
(c) g(i * ci);
解：

(a) int&
(b) const int&
(c) int&&

(a)：T 为int &，经折叠，val为int &

(b)：T 为const int & ，经折叠，val为const int &

(c)：T 为int &&，经折叠，val为int &&



// 43. 使用上一题定义的函数，如果我们调用g(i = ci),g 的模版参数将是什么？

解：

i = ci 返回的是左值，因此 g 的模版参数是 int&

表达式会留下左值，所以为int &




// 44. 使用与第一题中相同的三个调用，如果 g 的函数参数声明为 T（而不是T&&），确定T的类型。如果g的函数参数是 const T&呢？

解：

当声明为T的时候，T的类型为int&。 当声明为const T&的时候，T的类型为int&。


声明为T，值传递

(a)：T 为int

(b)：T为 int，const被忽略

(c)：T为 int

声明为const T&


(a)：T 为int

(b)：T为 int

(c)：T为 int&&


// 45
如果下面的模版，如果我们对一个像42这样的字面常量调用g，解释会发生什么？如果我们对一个int 类型的变量调用g 呢？

template <typename T> void g(T&& val) { vector<T> v; }
解：

当使用字面常量，T将为int。 当使用int变量，T将为int&。编译的时候将会报错，因为没有办法对这种类型进行内存分配，无法创建vector<int&>。


传递42，为右值，T被解析为右值，即int &&，折叠后，仍未int &&，正常

传递int 类型变量，为左值，T被解析为int &，折叠后，为int & ，这时将int & 传递给vector会出错，因为该左值没有初始化




// 46
解释下面的循环，它来自13.5节中的 StrVec::reallocate:

for (size_t i = 0; i != size(); ++i)
	alloc.construct(dest++, std::move(*elem++));
解：

在每个循环中，对 elem 的解引用操作 * 当中，会返回一个左值，std::move 函数将该左值转换为右值，提供给 construct 函数。
// 将elem的元素全部转到dest的空间中




// 47. 


// 49. 解释下面每个调用会发生什么：

template <typename T> void f(T);
template <typename T> void f(const T*);
template <typename T> void g(T);
template <typename T> void g(T*);
int i = 42, *p = &i;
const int ci = 0, *p2 = &ci;
g(42); g(p); g(ci); g(p2);
f(42); f(p); f(ci); f(p2);
解：

    g(42);    	//g(T )
    g(p);     	//g(T*)
    g(ci);      //g(T)   
    g(p2);      //g(T*)    
    f(42);    	//f(T)
    f(p);     	//f(T)
    f(ci);    	//f(T)
    f(p2);      //f(const T*)







// 50.



// 51. 调用本节中的每个 foo，确定 sizeof...(Args) 和 sizeof...(rest)分别返回什么。

解：

#include <iostream>

using namespace std;

template <typename T, typename ... Args>
void foo(const T &t, const Args& ... rest){
    cout << "sizeof...(Args): " << sizeof...(Args) << endl;
    cout << "sizeof...(rest): " << sizeof...(rest) << endl;
};

void test_param_packet(){
    int i = 0;
    double d = 3.14;
    string s = "how now brown cow";

    foo(i, s, 42, d);
    foo(s, 42, "hi");
    foo(d, s);
    foo("hi");
}

int main(){
    test_param_packet();
    return 0;
}
结果：

sizeof...(Args): 3
sizeof...(rest): 3
sizeof...(Args): 2
sizeof...(rest): 2
sizeof...(Args): 1
sizeof...(rest): 1
sizeof...(Args): 0
sizeof...(rest): 0










int main()
{
    Stack<char>* sc; // 没有实例化
    // f1(*sc);    // 没有实例化
    int iobj = sizeof(Stack<string>); // stack对string的实例化


    double *p = new double;
    Debugdelete d;
    d(p);

    vector<int> vi = {0, 1, 2, 3};
    print(vi);

    int arr_1[3] = {1, 2, 3};

    cout << "arr_size   " << arr_size(arr_1) << endl;

    cout << "第一个元素：" << *begin(arr_1) << endl;
    cout << "最后一个元素：" << *(end(arr_1) - 1) << endl;

    int arr[4] = {1, 2, 3, 4};
    my_print(arr);

    vector<int> vec1;
    vec1.push_back(2);
    vec1.push_back(3);
    if (my_find(vec1.begin(), vec1.end(), 2))
    {
        cout << "The element has been found ！" << endl;
    }
    else
    {
        cout << "Ops~~" << endl;
    }

    list<string> list1;
    list1.push_back("sad");
    list1.push_back("sand");
    if (my_find(list1.begin(), list1.end(), "sad"))
    {
        cout << "The element has been found ！" << endl;
    }
    else
    {
        cout << "Ops~~" << endl;
    }

    int res = compare(5, 6);
    cout << "res   " << res << endl;
    Sales_data s1, s2;

    // cout<<compare(s1,s2)<<endl;

    printf("hello c++ \n");
}